package libs;

/** <H> Java Tip 43: How to read 8- and 24-bit Microsoft Windows bitmaps in Java applications</H>
 <P>
 A step-by-step guide to loading bitmap files in a Java application
 <P>
 AUTHOR	Jeff West, with John D. Mitchell
 <P>
 Currently, only GIF and JPEG images are supported by the standard
 <code>getImage()</code> method. While Java routines exist for PNG
 (Portable Network Graphics) format images, we don't know of any reader
 existing for Microsoft Windows bitmap images.  This tip, authored by
 Jeff West, provides code for loading Windows bitmap images. 
 <em>(1,400 words)</em>
 <P>
 Windows bitmap files in Java applications is not
 officially supported in the current release of Java. But fear not,
 there is a way!  This tip will show you how to accomplish this task --
 starting with a description of the basic steps involved in reading the
 Microsoft Windows file format.
 <P>
 The Windows DIB (Device-Independent Bitmap) file format is relatively
 straightforward.  The DIB format preserves explicit information unlike
 the plain bitmap format which is used for storage of the image in RAM.
 The problem is that there are so many image format varieties (1, 4, 8,
 and 16 bits, among others).  The solution offered in this Java Tip will
 address only the 8- and 24-bit flavors.  These two represent the most
 widely encountered varieties.
 <P>
 <A HREF="/javaworld/javatips/jw-javatip42.html">
 <IMG ALIGN="right" BORDER="0" width="65" height="15" SRC="/javaworld/icons/b-nexttip.gif" ALT="[Next Tip]"></a>
 <A HREF="/javaworld/javatips/jw-javatips.index.html">
 <IMG ALIGN="right" BORDER="0" width="71" height="15" SRC="/javaworld/icons/b-tipindex.gif" ALT="[Tip Index]"></a>
 <P>
 Regardless of the Windows DIB sub-type, the file format always consists
 of a 14-byte file header and a 40-byte information header. These two
 headers contain information about exactly what is stored in the file
 and in what order. Consult the Microsoft Software Development Kit (SDK)
 for the exact and precise meaning of each item in the headers. The
 remaining file contents vary depending on the data in the information
 header.
 <P>
 Let's look at the two sub-types we're addressing here. The 24-bit
 flavor is very simple:  The RGB (Red-Green-Blue) color values (3 bytes,
 in BGR order) follow immediately after the information header. However,
 each scan line is padded out to an even 4-byte boundary.  This
 "padding" is documented (see Microsoft SDK) to be an optimization for
 the Windows bitmap drawing APIs.  Also, the bottom scan line is the
 first thing encountered in the file -- so the image has to be read
 backwards in relation to the usual graphics coordinate systems in which
 the positive vector orientation is down and to the right.
 <P>
 The 8-bit sub-type is complicated by the insertion of color palette
 information between the information header and the pixel data.
 Therefore, each pixel entry is simply an 8-bit index into the palette
 array of 24-bit RGB colors.  Once again, the pixel information is
 padded out to an even 4-byte boundary for each scan line.
 <P>
 Note that the bitmap image-loading method presented here does not
 support decompression of compressed bitmap images.  In fact, this
 routine does not even look for this possibility!
 This routine is sure to generate an exception if a compressed Windows DIB file is
 encountered. The compressed Windows DIB format is documented in the Windows
 SDK.
 <P>
 In terms of performance, this routine can read a 24-bit, 640 x 480 file
 (approximately 920 kilobytes) in less than 10 seconds on a
 486-DX2-66MHz running Microsoft Windows 95.  Performance can be
 signficantly enhanced by using <code>BufferedInputStream</code> instead
 of <code>FileInputStream</code>.
 <P>
 The following routine reads either of the two file formats and
 generates an <code>Image</code> object.  Comprehensive error and
 exception handling is not included below to keep from muddying the
 routine further. An unsupported Windows DIB sub-type can always be
 converted using the Windows Paint program.
 <P>
 loadbitmap() method converted from Windows C code.
 Reads only uncompressed 24- and 8-bit images.  Tested with
 images saved using Microsoft Paint in Windows 95.  If the image
 is not a 24- or 8-bit image, the program refuses to even try.
 I guess one could include 4-bit images by masking the byte
 by first 1100 and then 0011.  I am not really 
 interested in such images.  If a compressed image is attempted,
 the routine will probably fail by generating an IOException.
 Look for variable <B>ncompression</B> to be different from 0 to indicate
 compression is present.
 <P>
 Arguments:
 sdir and sfile are the result of the FileDialog()
 getDirectory() and getFile() methods.
 <P>
 Returns:
 Image Object, be sure to check for null !!!!
 */

import java.io.*;
import java.awt.*;
import java.awt.image.*;

public class LoadBitmap {
	
	public static boolean DEBUG=false;

	/**
	 * Create a Java Image from the named file.
	 * 
	 * @param sdir,
	 *            names the directory, ending with the separator character.
	 * @param sfile,
	 *            names the file,
	 * @param comp,
	 *            any Component as ImageObserver.
	 */

	public static Image loadbitmap(String sdir, String sfile, Component comp)
			throws FileNotFoundException {
		println("loading:" + sdir + sfile);
		return loadbitmap(new FileInputStream(sdir + sfile), comp);
	}
	
	private static void println(Object str){
		if (DEBUG) {
			System.out.println(str);
		}
	}

	public static Image loadbitmap(String filePath, Component comp)
			throws FileNotFoundException {
		println("loading:" + filePath);
		return loadbitmap(new FileInputStream(filePath), comp);
	}

	/**
	 * Create a Java Image from the open stream.
	 * 
	 * @param fs,
	 *            the open stream.
	 * @param comp,
	 *            any Component as ImageObserver.
	 */

	public static Image loadbitmap(FileInputStream fs, Component comp) {

		try {
			Image image;

			int bflen = 14; // 14 byte BITMAPFILEHEADER
			byte bf[] = new byte[bflen];
			fs.read(bf, 0, bflen);

			int bilen = 40; // 40-byte BITMAPINFOHEADER
			byte bi[] = new byte[bilen];
			fs.read(bi, 0, bilen);

			// Interperet data.

			int nsize = (((int) bf[5] & 0xff) << 24)
					| (((int) bf[4] & 0xff) << 16)
					| (((int) bf[3] & 0xff) << 8) | (int) bf[2] & 0xff;

			println("File type is :" + (char) bf[0] + (char) bf[1]);
			println("Size of file is :" + nsize);

			int nbisize = (((int) bi[3] & 0xff) << 24)
					| (((int) bi[2] & 0xff) << 16)
					| (((int) bi[1] & 0xff) << 8) | (int) bi[0] & 0xff;

			println("Size of bitmapinfoheader is :" + nbisize);

			int nwidth = (((int) bi[7] & 0xff) << 24)
					| (((int) bi[6] & 0xff) << 16)
					| (((int) bi[5] & 0xff) << 8) | (int) bi[4] & 0xff;

			println("Width is :" + nwidth);

			int nheight = (((int) bi[11] & 0xff) << 24)
					| (((int) bi[10] & 0xff) << 16)
					| (((int) bi[9] & 0xff) << 8) | (int) bi[8] & 0xff;

			println("Height is :" + nheight);

			int nplanes = (((int) bi[13] & 0xff) << 8) | (int) bi[12] & 0xff;

			println("Planes is :" + nplanes);

			int nbitcount = (((int) bi[15] & 0xff) << 8) | (int) bi[14] & 0xff;

			println("BitCount is :" + nbitcount);

			// Look for non-zero values to indicate compression

			int ncompression = (((int) bi[19]) << 24) | (((int) bi[18]) << 16)
					| (((int) bi[17]) << 8) | (int) bi[16];

			println("Compression is :" + ncompression);

			int nsizeimage = (((int) bi[23] & 0xff) << 24)
					| (((int) bi[22] & 0xff) << 16)
					| (((int) bi[21] & 0xff) << 8) | (int) bi[20] & 0xff;

			println("SizeImage is :" + nsizeimage);

			int nxpm = (((int) bi[27] & 0xff) << 24)
					| (((int) bi[26] & 0xff) << 16)
					| (((int) bi[25] & 0xff) << 8) | (int) bi[24] & 0xff;

			println("X-Pixels per meter is :" + nxpm);

			int nypm = (((int) bi[31] & 0xff) << 24)
					| (((int) bi[30] & 0xff) << 16)
					| (((int) bi[29] & 0xff) << 8) | (int) bi[28] & 0xff;

			println("Y-Pixels per meter is :" + nypm);

			int nclrused = (((int) bi[35] & 0xff) << 24)
					| (((int) bi[34] & 0xff) << 16)
					| (((int) bi[33] & 0xff) << 8) | (int) bi[32] & 0xff;

			println("Colors used are :" + nclrused);

			int nclrimp = (((int) bi[39] & 0xff) << 24)
					| (((int) bi[38] & 0xff) << 16)
					| (((int) bi[37] & 0xff) << 8) | (int) bi[36] & 0xff;

			println("Colors important are :" + nclrimp);

			// No Palatte data for 24-bit format but scan lines are
			// padded out to even 4-byte boundaries.

			if (nbitcount == 24) {

				int npad = (nsizeimage / nheight) - nwidth * 3;
				int ndata[] = new int[nheight * nwidth];
				byte brgb[] = new byte[(nwidth + npad) * 3 * nheight];
				fs.read(brgb, 0, (nwidth + npad) * 3 * nheight);
				int nindex = 0;

				for (int j = 0; j < nheight; j++) {
					for (int i = 0; i < nwidth; i++) {
						ndata[nwidth * (nheight - j - 1) + i] = (255 & 0xff) << 24
								| (((int) brgb[nindex + 2] & 0xff) << 16)
								| (((int) brgb[nindex + 1] & 0xff) << 8)
								| (int) brgb[nindex] & 0xff;

						println("Encoded Color at (" + i + "," + j
								+ ")is:" + " (R,G,B)= ("
								+ ((int) (brgb[2]) & 0xff) + ","
								+ ((int) brgb[1] & 0xff) + ","
								+ ((int) brgb[0] & 0xff) + ")");

						nindex += 3;
					}
					nindex += npad;
				}

				image = comp.createImage(new MemoryImageSource(nwidth, nheight,
						ndata, 0, nwidth));
			}

			// Have to determine the number of colors, the clrsused
			// parameter is dominant if it is greater than zero. If
			// zero, calculate colors based on bitsperpixel.

			else if (nbitcount == 8) {

				int nNumColors = 0;
				if (nclrused > 0)
					nNumColors = nclrused;

				else
					nNumColors = (1 & 0xff) << nbitcount;

				println("The number of Colors is" + nNumColors);

				// Some bitmaps do not have the sizeimage field calculated
				// Ferret out these cases and fix 'em.

				if (nsizeimage == 0) {
					nsizeimage = ((((nwidth * nbitcount) + 31) & ~31) >> 3);
					nsizeimage *= nheight;

					println("nsizeimage (backup) is" + nsizeimage);
				}

				// Read the palatte colors.
				int npalette[] = new int[nNumColors];
				byte bpalette[] = new byte[nNumColors * 4];
				fs.read(bpalette, 0, nNumColors * 4);
				int nindex8 = 0;
				for (int n = 0; n < nNumColors; n++) {

					npalette[n] = (255 & 0xff) << 24
							| (((int) bpalette[nindex8 + 2] & 0xff) << 16)
							| (((int) bpalette[nindex8 + 1] & 0xff) << 8)
							| (int) bpalette[nindex8] & 0xff;

					println("Palette Color " + n + " is:"
							+ npalette[n] + " (res,R,G,B)= ("
							+ ((int) (bpalette[nindex8 + 3]) & 0xff) + ","
							+ ((int) (bpalette[nindex8 + 2]) & 0xff) + ","
							+ ((int) bpalette[nindex8 + 1] & 0xff) + ","
							+ ((int) bpalette[nindex8] & 0xff) + ")");
					nindex8 += 4;
				}

				// Read the image data (actually indices into the palette)
				// Scan lines are still padded out to even 4-byte
				// boundaries.

				int npad8 = (nsizeimage / nheight) - nwidth;

				println("nPad is:" + npad8);

				int ndata8[] = new int[nwidth * nheight];
				byte bdata[] = new byte[(nwidth + npad8) * nheight];
				fs.read(bdata, 0, (nwidth + npad8) * nheight);
				nindex8 = 0;
				for (int j8 = 0; j8 < nheight; j8++) {
					for (int i8 = 0; i8 < nwidth; i8++) {
						ndata8[nwidth * (nheight - j8 - 1) + i8] = npalette[((int) bdata[nindex8] & 0xff)];
						nindex8++;
					}
					nindex8 += npad8;
				}

				image = comp.createImage(new MemoryImageSource(nwidth, nheight,
						ndata8, 0, nwidth));
			}

			else {
				System.out
						.println("Not a 24-bit or 8-bit Windows Bitmap, aborting...");
				image = (Image) null;
			}

			fs.close();
			return image;
		} catch (Exception e) {
			println("Caught exception in loadbitmap!");
		}
		return (Image) null;
	}

}
/* <IMG SRC="/cgi-bin/counter"> */
